// =============================================================================
//  STATS - Statistical Analysis Tools, v.0.01
//  Copyright (C) 2012  Marco Vettigli
// -----------------------------------------------------------------------------
//
// This program  is free software:  you can  redistribute it and/or  modify it
// under the terms of the  GNU General Public License as published by the Free
// Software Foundation,  either version 3 of the License,  or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY;  without even  the  implied  warranty  of  MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.  See the  GNU General Public License  for
// more details.
//
// You  should  have received a copy of the  GNU General Public License  along
// with this program.  If not, see <http://www.gnu.org/licenses/>.
//
// == module ===================================================================
//
// STABLEWIDGET.CPP
// The class STableWidget is the StatS user graphical interface to visualise
// and manage data of STable object.
//
// Author: Marco Vettigli
//
// The class manage visualisation and modification of undeling STable object.
// The widget is thought to be used as closable tab in the MainWindow class.
// It is composed by a QTableView showing data in tabular form and a
// QTreeWidget showing column and row summary data.
// Both headers of the table are bound to context menu to allow data structural
// modification on selected column/row properties.
//
// Selection is customized and behaves as column-row crossed selection.
// Multiple selection is allowed through pressing of key 'Ctrl' and 'Shift'
//
// == implementation ===========================================================
//
// TODO: write STableWidget implementation documentation
//
// =============================================================================

// == include directives =======================================================

#include <QStringList>
#include <QtGui>
#include <QtCore>
#include <QHash>
#include <QDebug>
#include <QColor>
#include "stablewidget.h"
#include "gui/dialogcolumnproperties.h"
#include "gui/dialoginsertcolumns.h"
#include "gui/dialoginsertrows.h"
#include "utils.h"

// == constructors and destructors =============================================

StatS::STableWidget::STableWidget(QWidget* parent,
                                  StatS::STable* table)
    : QWidget(parent)
{
    setupUI();

    // define table and bind it to tableView
    if (table == 0) table = new StatS::STable();
    this->_table = table;
    tableView->setModel(this->_table);
    tableView->setSelectionMode(QAbstractItemView::NoSelection);
    // define keystate and selection buffers
    _keyState = Qt::Key_unknown;
    _colBuffer = -1;
    _rowBuffer = -1;
    // connect selection changed
    connect(_table, SIGNAL(headerDataChanged(Qt::Orientation,int,int)),
            this, SLOT(on_headerDataChanged(Qt::Orientation,int,int)));
    // update summary tree
    updateSummaryTree();
}

// == getters ==================================================================

StatS::STable * StatS::STableWidget::table() const
{
    return _table;
}

QString StatS::STableWidget::name() const
{
    if (_table != 0) return _table->name();
    else return QString();
}

// == setters ==================================================================

// == inspectors ===============================================================

// == accessor function definitions ============================================


void StatS::STableWidget::cutSelection()
{
    on_cellsCutting();
}

void StatS::STableWidget::pasteClipboard()
{
    on_cellsPasting();
}

void StatS::STableWidget::copySelection()
{
    on_cellsCopying();
}

void StatS::STableWidget::clearSelection()
{
    on_cellsClearing();
}

void StatS::STableWidget::updateSummaryTree()
{
    QColor ribbonBackColor = QColor(107, 114, 179);
    QColor ribbonForeColor = QColor(255, 255, 255);
    QColor selectedBackColor = QColor(180, 183, 207);
    QColor unselectedBackColor = QColor(255, 255, 255);
    // save expanded states
    bool eColNode = true;
    bool eRowNode = true;
    bool eSeriesNode = false;
    bool eMarkersNode = false;
    bool eColorsNode = false;
    bool eSizesNode = false;
    if (tableSummary->topLevelItemCount() != 0) {
        eColNode = tableSummary->topLevelItem(0)->isExpanded();
        eRowNode = tableSummary->topLevelItem(1)->isExpanded();
        eSeriesNode = tableSummary->topLevelItem(1)->child(0)->isExpanded();
        eMarkersNode = tableSummary->topLevelItem(1)->child(1)->isExpanded();
        eColorsNode = tableSummary->topLevelItem(1)->child(2)->isExpanded();
        eSizesNode = tableSummary->topLevelItem(1)->child(3)->isExpanded();
    }


    // clear all items in tableSummary tree
    tableSummary->clear();
    // define column QTreeWidgetItem
    QTreeWidgetItem* nodeCols = new QTreeWidgetItem(tableSummary);
    nodeCols->setText(0, QString("Columns (%1,%2)")
                      .arg(_table->columnCount())
                      .arg(_table->selectedColumns().size()));
    nodeCols->setText(1, "columns");
    nodeCols->setBackgroundColor(0, ribbonBackColor);    
    nodeCols->setForeground(0, ribbonForeColor);
    nodeCols->setExpanded(eColNode);
    tableSummary->addTopLevelItem(nodeCols);
    // define row QTreeWidgetItem
    QTreeWidgetItem* nodeRows = new QTreeWidgetItem(tableSummary);
    nodeRows->setText(0, QString("Rows (%1,%2)")
                      .arg(_table->rowCount())
                      .arg(_table->selectedRows().size()));
    nodeRows->setText(1, "rows");
    nodeRows->setBackgroundColor(0, ribbonBackColor);
    nodeRows->setForeground(0, ribbonForeColor);
    nodeRows->setExpanded(eRowNode);
    tableSummary->addTopLevelItem(nodeRows);
    // add columns to column node
    QTreeWidgetItem *item;
    for(int i = 0; i < _table->columnCount(); i++) {
        // create new item and set text and selection properties
        item = new QTreeWidgetItem(nodeCols);
        item->setText(0, _table->headerData(i, Qt::Horizontal).toString());
        item->setText(1, QString("column,%1").arg(i));
        item->setBackgroundColor(0, _table->getColumnProperty(i)->isSelected() ?
                                     selectedBackColor :
                                     unselectedBackColor);
        item->setToolTip(0, QString("%1%2")
                         .arg(_table->getColumnName(i))
                         .arg(_table->getColumnProperty(i)->getToolTip()));
        // set new item icon based on column type
        switch (_table->getColumnType(i)) {
        case StatS::SColProperty::SDoubles:
            item->setIcon(0, QIcon(":/images/numeric"));
            break;
        case StatS::SColProperty::SStrings:
            item->setIcon(0, QIcon(":/images/character"));
            break;
        default: break;
        }
        nodeCols->addChild(item);
    }
    // declare hash tables for storing categories
    QHash<int, QString> sizeTable = QHash<int, QString>();
    QHash<QString, QString> colorTable = QHash<QString, QString>();
    QHash<int, QString> markerTable = QHash<int, QString>();
    QHash<QString, QString> propertyTable = QHash<QString, QString>();
    // populate hash tables
    for (int i = 0; i < _table->rowCount(); i++) {
        const StatS::SRowProperty* property = _table->getRowProperty(i);
        sizeTable[property->size()].append(QString("%1,").arg(i));
        colorTable[property->color().name()].append(QString("%1,").arg(i));
        markerTable[(int)property->marker()].append(QString("%1,").arg(i));
        QString propertyString = QString("%1|%2|%3")
                .arg(property->marker())
                .arg(property->color().name())
                .arg(property->size());
        if (property->size() != 0)
            propertyTable[propertyString].append(QString("%1,").arg(i));
    }
    // defines series QTreeWidgetItems
    QTreeWidgetItem* nodeSeries = new QTreeWidgetItem(nodeRows);
    nodeSeries->setText(0, QString("series (%1)").arg(propertyTable.size()));
    nodeSeries->setText(1, "header");
    nodeSeries->setExpanded(eSeriesNode);
    nodeRows->addChild(nodeSeries);
    // defines markers QTreeWidgetItems
    QTreeWidgetItem* nodeMarkers = new QTreeWidgetItem(nodeRows);
    nodeMarkers->setText(0, QString("markers (%1)").arg(markerTable.size()));
    nodeMarkers->setText(1, "header");
    nodeMarkers->setExpanded(eMarkersNode);
    nodeRows->addChild(nodeMarkers);
    // defines colors QTreeWidgetItems
    QTreeWidgetItem* nodeColors = new QTreeWidgetItem(nodeRows);
    nodeColors->setText(0, QString("colors (%1)").arg(colorTable.size()));
    nodeColors->setText(1, "header");
    nodeColors->setExpanded(eColorsNode);
    nodeRows->addChild(nodeColors);
    // defines sizes QTreeWidgetItems
    QTreeWidgetItem* nodeSizes = new QTreeWidgetItem(nodeRows);
    nodeSizes->setText(0, QString("sizes (%1)").arg(sizeTable.size()));
    nodeSizes->setText(1, "header");
    nodeSizes->setExpanded(eSizesNode);
    nodeRows->addChild(nodeSizes);
    // add marker categories
    QHashIterator<int, QString> im(markerTable);
    StatS::SRowProperty prop = StatS::SRowProperty();
    while(im.hasNext()) {
        im.next();
        // remove trailing colon
        QString refLabel = im.value();
        refLabel.chop(1);
        // get number of rows
        int num = refLabel.split(',').size();
        item = new QTreeWidgetItem(nodeMarkers);
        prop.setMarker(StatS::SRowProperty::RowMarker(im.key()));
        item->setIcon(0, QIcon(prop.getPixmap()));
        item->setText(0, QString("%1 %2")
                      .arg(num)
                      .arg(num == 1 ? "row" : "rows"));
        item->setText(1, QString("row,%1").arg(refLabel));
        nodeMarkers->addChild(item);
    }
    // add color categories
    QHashIterator<QString, QString> ic(colorTable);
    while(ic.hasNext()) {
        ic.next();
        // remove trailing colon
        QString refLabel = ic.value();
        refLabel.chop(1);
        // get number of rows
        int num = refLabel.split(',').size();
        item = new QTreeWidgetItem(nodeColors);
        prop.setColor(QColor(ic.key()));
        item->setIcon(0, QIcon(prop.getColorPixmap()));
        item->setText(0, QString("%1 %2")
                      .arg(num)
                      .arg(num == 1 ? "row" : "rows"));
        item->setText(1, QString("row,%1").arg(refLabel));
        nodeColors->addChild(item);
    }

    // add size categories
    QHashIterator<int, QString> is(sizeTable);
    while(is.hasNext()) {
        is.next();
        // remove trailing colon
        QString refLabel = is.value();
        refLabel.chop(1);
        // get number of rows
        int num = refLabel.split(',').size();
        // set QTreeWidgetItem properties
        item = new QTreeWidgetItem(nodeSizes);
        item->setText(1, QString("row,%1").arg(refLabel));
        switch ((int)is.key()) {
        case 0:            
            item->setText(0, QString("hidden(%1)").arg(num));
            break;
        case 1:
            item->setText(0, QString("small[%1]").arg(num));
            break;
        case 3:
            item->setText(0, QString("medium[%1]").arg(num));
            break;
        case 5:
            item->setText(0, QString("large[%1]").arg(num));
            break;
        default:
            item->setText(0, QString("size%1[%2]").arg(is.key()).arg(num));
        }
        nodeSizes->addChild(item);
    }
    // add property categories
    QHashIterator<QString, QString> ip(propertyTable);
    while(ip.hasNext()) {
        ip.next();
        // remove trailing colon
        QString refLabel = ip.value();
        refLabel.chop(1);
        // get number of rows
        int num = refLabel.split(',').size();
        QStringList propList = ip.key().split('|');
        item = new QTreeWidgetItem(nodeSeries);
        prop.setMarker(StatS::SRowProperty::RowMarker(propList.at(0).toInt()));
        prop.setColor(QColor(propList[1]));
        prop.setSize(propList.at(2).toInt());
        item->setIcon(0, QIcon(prop.getPixmap()));
        item->setText(0, QString("%1 %2")
                      .arg(num)
                      .arg(num == 1 ? "row" : "rows"));
        item->setText(1, QString("row,%1").arg(refLabel));
        nodeSeries->addChild(item);
    }
}

void StatS::STableWidget::keyPressEvent(QKeyEvent *event)
{
    _keyState = Qt::Key(event->key());
}

void StatS::STableWidget::keyReleaseEvent(QKeyEvent* /*event*/)
{
    _keyState = Qt::Key_unknown;
}

// == static variables =========================================================

// == static funtion definitions ===============================================

// == private function definitions =============================================

void StatS::STableWidget::setupUI()
{
    // create Layout and splitters
    QSplitter* hsplitter = new QSplitter(Qt::Horizontal, this);
    QVBoxLayout* mainlayout = new QVBoxLayout(this);
    // initialize tableSummary
    tableSummary = new QTreeWidget(hsplitter);
    tableSummary->setHeaderLabel("Table summary");
    tableSummary->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding);
    tableSummary->setMinimumWidth(150);
    tableSummary->setSelectionMode(QAbstractItemView::NoSelection);
    // initialize QTableView
    tableView = new QTableView(this);
    tableView->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu);
    tableView->verticalHeader()->setContextMenuPolicy(Qt::CustomContextMenu);
    tableView->setContextMenuPolicy(Qt::CustomContextMenu);
    tableView->setFont(QFont("Monospace", 9, QFont::Normal));
    tableView->verticalHeader()->setDefaultSectionSize(25);
    // populate hsplitter
    hsplitter->addWidget(tableSummary);
    hsplitter->addWidget(tableView);
    hsplitter->setStretchFactor(0,1);
    hsplitter->setStretchFactor(1,10);
    // populate layout
    mainlayout->addWidget(hsplitter);
    // set layout of the QWidget
    setLayout(mainlayout);
    //layout();
    //show();
    // row and column double clicks
    connect(tableView->horizontalHeader(),
            SIGNAL(sectionDoubleClicked(int)),
            this, SLOT(on_horizontalHeader_DoubleClick(int)));
    connect(tableView->verticalHeader(),
            SIGNAL(sectionDoubleClicked(int)),
            this, SLOT(on_verticalHeader_DoubleClick(int)));
    // row and column context menus
    connect(tableView->horizontalHeader(),
            SIGNAL(customContextMenuRequested(QPoint)),
            this, SLOT(on_horizontalHeader_ContextMenu(QPoint)));
    connect(tableView->verticalHeader(),
            SIGNAL(customContextMenuRequested(QPoint)),
            this, SLOT(on_verticalHeader_ContextMenu(QPoint)));    
    // cell context menu
    connect(tableView, SIGNAL(customContextMenuRequested(QPoint)),
            this, SLOT(on_contextMenu(QPoint)));
    // row and column header selection
    connect(tableView->horizontalHeader(), SIGNAL(sectionClicked(int)),
            this, SLOT(on_colSelecting(int)));
    connect(tableView->verticalHeader(), SIGNAL(sectionClicked(int)),
            this, SLOT(on_rowSelecting(int)));
    // cell selection
    connect(tableView, SIGNAL(clicked(QModelIndex)),
            this, SLOT(on_cellSelection(QModelIndex)));
    // table summary selection
    connect(tableSummary, SIGNAL(itemClicked(QTreeWidgetItem*,int)),
            this, SLOT(on_tableSummaryItemClicked(QTreeWidgetItem*, int)));

}

void StatS::STableWidget::on_horizontalHeader_DoubleClick(int index)
{
    if (index != -1) {
        _table->clearSelection();
        _table->selectColumn(index);
        on_columnPropertiesChanging();
    }
    else _table->insertColumn(_table->columnCount());    
    return;
}

void StatS::STableWidget::on_verticalHeader_DoubleClick(int index)
{
    if (index != -1) {
        _table->clearSelection();
        _table->selectRow(index);
        /* row properties*/
    }
    else _table->insertRows(_table->rowCount(), 1);
    return;

}

void StatS::STableWidget::on_verticalHeader_ContextMenu(QPoint point)
{
    // check if selected rows are present
    if (_table->selectedRows().size() == 0) {
        _table->clearSelection();
        if (tableView->indexAt(point).row() == -1) return;
        else _table->selectRow(tableView->indexAt(point).row());
    }
    // create context menu and all submenus
    QMenu *rowContextMenu = new QMenu(tableView);
    QMenu *markerContextMenu = new QMenu("Marker", rowContextMenu);
    QMenu *colorContextMenu = new QMenu("Color", rowContextMenu);
    QMenu *sizeContextMenu = new QMenu("Size", rowContextMenu);
    // populate marker context menu
    markerContextMenu->addAction("Dot", this, SLOT(on_rowMarkerChanging()));
    markerContextMenu->addAction("Circle", this, SLOT(on_rowMarkerChanging()));
    markerContextMenu->addAction("Square", this, SLOT(on_rowMarkerChanging()));
    markerContextMenu->addAction("Triangle", this, SLOT(on_rowMarkerChanging()));
    // populate marker context menu
    colorContextMenu->addAction("Black", this, SLOT(on_rowColorChanging()));
    colorContextMenu->addAction("Blue", this, SLOT(on_rowColorChanging()));
    colorContextMenu->addAction("Red", this, SLOT(on_rowColorChanging()));
    colorContextMenu->addAction("Green", this, SLOT(on_rowColorChanging()));
    colorContextMenu->addAction("Yellow", this, SLOT(on_rowColorChanging()));
    // populate marker context menu
    sizeContextMenu->addAction("Medium", this, SLOT(on_rowSizeChanging()));
    sizeContextMenu->addAction("Hidden", this, SLOT(on_rowSizeChanging()));
    sizeContextMenu->addAction("Small", this, SLOT(on_rowSizeChanging()));
    sizeContextMenu->addAction("Large", this, SLOT(on_rowSizeChanging()));
    // populate context menu
    rowContextMenu->addMenu(markerContextMenu);
    rowContextMenu->addMenu(colorContextMenu);
    rowContextMenu->addMenu(sizeContextMenu);
    rowContextMenu->addSeparator();
    rowContextMenu->addAction("Cut");
    rowContextMenu->addAction("Copy");
    rowContextMenu->addAction("Paste");
    rowContextMenu->addSeparator();
    rowContextMenu->addAction("Insert rows", this, SLOT(on_rowsInserting()));
    rowContextMenu->addAction("Remove rows", this, SLOT(on_rowsRemoving()));
    // cast context menu at mouse point
    rowContextMenu->exec(tableView->mapToGlobal(point));
}

void StatS::STableWidget::on_horizontalHeader_ContextMenu(QPoint point)
{
    // check if selected columns are present
    if (_table->selectedColumns().size() == 0) {
        _table->clearSelection();
        if (tableView->indexAt(point).column() == -1) return;
        else _table->selectColumn(tableView->indexAt(point).column());
    }
     // create context menu and all submenus
    QMenu *colContextMenu = new QMenu(tableView);
    QMenu *colContentMenu = new QMenu(colContextMenu);
    // populate Content submenu
    colContentMenu->setTitle("Content");
    colContentMenu->addAction("Numeric", this, SLOT(on_columnsConverting()));
    colContentMenu->addAction("Character", this, SLOT(on_columnsConverting()));
    // populate context menu
    colContextMenu->addAction("Properties", this, SLOT(on_columnPropertiesChanging()));
    colContextMenu->addMenu(colContentMenu);
    colContextMenu->addSeparator();
    colContextMenu->addAction("Cut");
    colContextMenu->addAction("Copy");
    colContextMenu->addAction("Paste");
    colContextMenu->addSeparator();
    colContextMenu->addAction("Insert columns", this, SLOT(on_columnInserting()));
    colContextMenu->addAction("Remove columns", this, SLOT(on_columnsRemoving()));
    // cast context menu at mouse point
    colContextMenu->exec(tableView->mapToGlobal(point));
}

void StatS::STableWidget::on_contextMenu(QPoint point)
{
    // check if there are selected columns/rows
    QModelIndex index = tableView->indexAt(point);
    if (index == _table->index(-1, -1)) return;
    if (_table->selectedColumns().size() == 0 &&
            _table->selectedRows().size() == 0) {
        // select cell pointer by mouse
        _table->selectColumn(index.column());
        _table->selectRow(index.row());
    }
     // create context menu and all submenus
    QMenu *colContextMenu = new QMenu();
    colContextMenu->addAction("Cut", this, SLOT(on_cellsCutting()));
    colContextMenu->addAction("Copy", this, SLOT(on_cellsCopying()));
    colContextMenu->addAction("Paste", this, SLOT(on_cellsPasting()));
    colContextMenu->addAction("Clear", this, SLOT(on_cellsClearing()));
    // cast context menu at mouse point
    colContextMenu->exec(tableView->mapToGlobal(point));
    delete colContextMenu;
}

void StatS::STableWidget::on_rowMarkerChanging()
{
    StatS::SRowProperty::RowMarker marker;
    // set marker based on user choice
    QString action_name = ((QAction*)sender())->text();
    if (action_name == "Dot") marker = StatS::SRowProperty::Dot;
    if (action_name == "Circle") marker = StatS::SRowProperty::Circle;
    if (action_name == "Square") marker = StatS::SRowProperty::Square;
    if (action_name == "Triangle") marker = StatS::SRowProperty::Triangle;
    // get a list of selected rows
    QList<int> selectedRows = _table->selectedRows();
    // set rows marker
    foreach(int index, selectedRows) {
        _table->setRowMarker(index, marker);
    }    
    return;
}

void StatS::STableWidget::on_rowColorChanging()
{
    QColor color;
    // set marker based on user choice
    QString action_name = ((QAction*)sender())->text();
    if (action_name == "Black") color = Qt::black;
    if (action_name == "Blue") color = Qt::blue;
    if (action_name == "Red") color = Qt::red;
    if (action_name == "Green") color = Qt::green;
    if (action_name == "Yellow") color = Qt::yellow;
    // get a list of selected rows
    QList<int> selectedRows = _table->selectedRows();
    // set rows color
    foreach(int index, selectedRows) {
        _table->setRowColor(index, color);
    }    
    return;
}

void StatS::STableWidget::on_rowSizeChanging()
{
    int size = 3;
    // set marker based on user choice
    QString action_name = ((QAction*)sender())->text();
    if (action_name == "Medium") size = 3;
    if (action_name == "Hidden") size = 0;
    if (action_name == "Small") size = 1;
    if (action_name == "Large") size = 5;
    // get a list of selected rows
    QList<int> selectedRows = _table->selectedRows();
    // set rows marker
    foreach(int index, selectedRows) {
        _table->setRowSize(index, size);
    }
    // update accessor lists and return
    updateSummaryTree();
    return;
}

void StatS::STableWidget::on_columnPropertiesChanging()
{
    // get a list of selected columns
    QList<int> selectedColumns = _table->selectedColumns();
    // define of needed variables
    QString col_name, col_description, col_units;
    StatS::SColProperty::ArrayType col_type = StatS::SColProperty::Undefined;
    double col_lsl = 0;
    double col_usl = 0;
    // set dialog arguments on selected columns properties
    if (selectedColumns.size() == 1) {
        // get column properties
        int selectedIndex = selectedColumns.at(0);
        col_name = _table->headerData(selectedIndex, Qt::Horizontal).toString();
        col_type = _table->getColumnType(selectedIndex);
        col_description = _table->getColumnProperty(selectedIndex)
                ->description();
        col_lsl = _table->getColumnProperty(selectedIndex)->lsl();
        col_usl = _table->getColumnProperty(selectedIndex)->usl();
        col_units = _table->getColumnProperty(selectedIndex)->units();
    }
    // initialize new dialog
    DialogColumnProperties* dialog = new DialogColumnProperties(this);
    dialog->setColumnName(col_name);
    dialog->setColumnType(col_type);
    dialog->setDescription(col_description);
    dialog->setUnits(col_units);
    dialog->setLsl(col_lsl);
    dialog->setUsl(col_usl);
    // change column properties if user confirms
    if (dialog->exec() == QDialog::Accepted) {
        // get information from dialog
        QString nCol_name = dialog->getColumnName();
        StatS::SColProperty::ArrayType nCol_type = dialog->getColumnType();
        QString nCol_description = dialog->getDescription();
        double nCol_lsl = dialog->getLsl();
        double nCol_usl = dialog->getUsl();
        QString nCol_units = dialog->getUnits();
        // change column name if user changed them
        int untitled_number = 1;
        if (nCol_name != col_name) {
            foreach (int index, selectedColumns) {
                QString columnName = selectedColumns.size() > 1 ?
                            QString("%1%2").arg(nCol_name).arg(untitled_number)
                          : nCol_name;
                _table->setHeaderData(index, Qt::Horizontal, columnName);
                untitled_number++;
            }
        }
        // change column content if user changed them
        if (nCol_type != col_type) {
            foreach (int index, selectedColumns) {
                if (!_table->isColumnConvertable(index, nCol_type)) {
                    if (QMessageBox::Yes == QMessageBox::warning(
                                this, "Warning", QString("Data loss will take "
                                "place upon %1 type convertion. Do you want to "
                                "continue? ").arg(_table->headerData(
                                index, Qt::Horizontal).toString()),
                                QMessageBox::No | QMessageBox::Yes))
                        _table->convertColumn(index, nCol_type);
                } else _table->convertColumn(index, nCol_type);

            }
        }
        if (nCol_description != col_description) {
            foreach (int index, selectedColumns) {
                _table->setColumnDescription(index, nCol_description);
            }
        }
        if (nCol_units != col_units) {
            foreach (int index, selectedColumns) {
                _table->setColumnUnits(index, nCol_units);
            }
        }
        if (nCol_usl != col_usl) {
            foreach (int index, selectedColumns) {
                _table->setColumnUsl(index, nCol_usl);
            }
        }
        if (nCol_lsl != col_lsl) {
            foreach (int index, selectedColumns) {
                _table->setColumnLsl(index, nCol_lsl);
            }
        }
    }
    // destroy dialog
    delete dialog;    
    return;
}

void StatS::STableWidget::on_columnInserting()
{
    // get a list of selected columns
    QList<int> selectedColumns = _table->selectedColumns();
    if (selectedColumns.size() == 0) return;
    // initialize new DialogInsertColumns dialog
    DialogInsertColumns *dialog = new DialogInsertColumns(this);
    dialog->setColumnName(_table->untitledColumnName());
    dialog->setColumnType(StatS::SColProperty::SDoubles);
    dialog->setInsertionPosition(DialogInsertColumns::BeforeSelected);
    dialog->setColumnNumber(1);
    // insert columns if user confirms
    if (dialog->exec() == QDialog::Accepted) {

        switch (dialog->getInsertionPosition()) {
        case DialogInsertColumns::Begin:
            _table->insertColumns(0, dialog->getColumnNumber(),
                                 dialog->getColumnType(),
                                 dialog->getColumnName());
            break;
        case DialogInsertColumns::End:
            _table->insertColumns(_table->columnCount(),
                                 dialog->getColumnNumber(),
                                 dialog->getColumnType(),
                                 dialog->getColumnName());
            break;
        default:
            QString columnName;
            for(int i = 0; i < selectedColumns.size(); i++) {
                if (selectedColumns.size() == 1)
                    columnName = dialog->getColumnName();
                else columnName = QString("%1%2")
                        .arg(dialog->getColumnName())
                        .arg(i+1);
                switch (dialog->getInsertionPosition()) {
                case DialogInsertColumns::AfterSelected:
                    _table->insertColumns(selectedColumns.at(i) + 1 + i
                                          * dialog->getColumnNumber(),
                                          dialog->getColumnNumber(),
                                          dialog->getColumnType(),
                                          columnName);
                    break;
                case DialogInsertColumns::BeforeSelected:
                    _table->insertColumns(selectedColumns.at(i) + i
                                          * dialog->getColumnNumber(),
                                          dialog->getColumnNumber(),
                                          dialog->getColumnType(),
                                          columnName);
                    break;
                default: break;
                }
            }
        }
    }
    return;
}

void StatS::STableWidget::on_columnsRemoving()
{
    // get a list of selected columns
    QList<int> selectedColumns = _table->selectedColumns();
    if (selectedColumns.size() == 0) return;
    // remove each column selected
    int removed_columns = 0;
    foreach(int index, selectedColumns) {
        _table->removeColumn(index - removed_columns);
        removed_columns++;
    }
    return;
}

void StatS::STableWidget::on_rowsInserting()
{
    // get a list of selected rows    
    QList<int> selectedRows = _table->selectedRows();
    if (selectedRows.size() == 0) return;
    // declare dialog to define insertion arguments
    DialogInsertRows* dialog = new DialogInsertRows(this);
    if (dialog->exec() == QDialog::Accepted) {
        int rowNumber = dialog->getRowNumber(), i = 0;
        // add rows for each selected row
        switch (dialog->getInsertionPosition()) {
        case DialogInsertRows::AfterSelected:
            foreach(int index, selectedRows) {
                _table->insertRows(index + i + 1, rowNumber);
                i += rowNumber;
            }
            break;
        case DialogInsertRows::BeforeSelected:
            foreach(int index, selectedRows) {
                _table->insertRows(index + i, rowNumber);
                i += rowNumber;
            }
            break;
        case DialogInsertRows::Begin:
            _table->insertRows(0, rowNumber);
            break;
        case DialogInsertRows::End:
            _table->insertRows(_table->rowCount(), rowNumber);
            break;
        default: return;
        }
    }
    delete dialog;
    return;
}

void StatS::STableWidget::on_rowsRemoving()
{
    // get a list of selected rows
    QList<int> selectedRows = _table->selectedRows();
    if (selectedRows.size() == 0) return;
    // remove all selected row
    int removed_rows = 0;
    foreach(int index, selectedRows) {
        _table->removeRow(index - removed_rows);
        removed_rows++;
    }    
    return;
}

void StatS::STableWidget::on_columnsConverting()
{
    // select type convertion based on menu action
    StatS::SColProperty::ArrayType type;
    QString action_name = ((QAction*)sender())->text();
    if (action_name == "Numeric") type = StatS::SColProperty::SDoubles;
    if (action_name == "Character") type = StatS::SColProperty::SStrings;
    // get a list of selected columns
    QList<int> selectedCols = _table->selectedColumns();
    // convert each selected column
    foreach(int index, selectedCols) {
        // check if the column type is already converted
        if (_table->getColumnType(index) == type) continue;
        // check if data loss will take place and ask for confirm
        if (!_table->isColumnConvertable(index, type)) {
            if (QMessageBox::Yes == QMessageBox::warning(
                    this, "Warning", "Data loss will take place upon "
                    "column type convertion. Do you want to continue? ",
                    QMessageBox::No | QMessageBox::Yes))
                _table->convertColumn(index, type);
        } else _table->convertColumn(index, type);
    }
    // update accessor lists and return    
    return;
}

void StatS::STableWidget::on_colSelecting(int col)
{    
    // handle multiple selection
    if (_keyState == Qt::Key_Shift) {
        if (_colBuffer == -1) {
            // update buffer
            _colBuffer = col;
            return;
        } else {            
            // depending on selection direction, select the range
            QList<int> colList = StatS::Utils::getRangeList(col, _colBuffer);
            _table->selectColumns(colList, true);
            // update buffer
            _colBuffer = col;
            return;
        }
    }
    // check if selection has to be cancelled
    bool isSelected = _table->getColumnProperty(col)->isSelected();
    if (_keyState != Qt::Key_Control) {
        _table->clearColumnSelection();
        if (isSelected) _table->selectColumn(col);
    }
    _table->selectColumn(col);
    // update buffer
    _colBuffer = col;
    return;
}

void StatS::STableWidget::on_rowSelecting(int row)
{
    // handle multiple selection
    if (_keyState == Qt::Key_Shift) {
        if (_rowBuffer == -1) {
            // update buffer
            _rowBuffer = row;
            return;
        } else {
            // depending on selection direction, select the range
            QList<int> rowList = StatS::Utils::getRangeList(row, _rowBuffer);
            _table->selectRows(rowList, true);
            // update buffer
            _rowBuffer = row;
            return;
        }
    }
    // check if selection has to be cancelled
    bool isSelected = _table->getRowProperty(row)->isSelected();
    if (_keyState != Qt::Key_Control) {
        _table->clearRowSelection();
        if (isSelected) _table->selectRow(row);
    }
    _table->selectRow(row);
    // update buffer
    _rowBuffer = row;
    return;
}

void StatS::STableWidget::on_cellSelection(QModelIndex index)
{
    // handle multiple selection
    if (_keyState == Qt::Key_Shift) {
        if (_colBuffer == -1 || _rowBuffer == -1) {
            // update buffers
            _colBuffer = index.column();
            _rowBuffer = index.row();
            return;
        } else {            
            // select columns
            QList<int> colList = StatS::Utils::getRangeList(
                        index.column(), _colBuffer);
            _table->selectColumns(colList , true);
            // select rows
            QList<int> rowList = StatS::Utils::getRangeList(
                        index.row(), _rowBuffer);
            _table->selectRows(rowList , true);
            // update buffers
            _rowBuffer = index.row();
            _colBuffer = index.column();
            return;
        }
    }
    if (_keyState != Qt::Key_Control) {
        _table->clearSelection();
    }
    // select column and row..
    _table->selectColumn(index.column(), true);
    _table->selectRow(index.row(), true);
    // .. and update buffers
    _rowBuffer = index.row();
    _colBuffer = index.column();
    return;
}

void StatS::STableWidget::on_headerDataChanged(Qt::Orientation /*orientation*/,
                                               int /*section*/,
                                               int /*role*/)
{
    updateSummaryTree();
}

void StatS::STableWidget::on_cellsCopying()
{    
    // get selected rows and columns
    QList<int> selCols = _table->selectedColumns();
    QList<int> selRows = _table->selectedRows();
    int rows = _table->rowCount();
    int cols = _table->columnCount();
    // chech if selection is null (not possible by default)
    if (selCols.size() == 0 && selRows.size() == 0) {
        QMessageBox::information(
                    this, "Data not selected",
                    "Please select data to be copied.",
                    QMessageBox::Ok);
        return;
    }
    // check if only columns are selected and fill all rows in the list
    if (selRows.size() == 0)
        for (int row = 0; row < rows; row++) selRows.append(row);
    // check if only rows are selected and fill all columns in the list
    if (selCols.size() == 0)
        for (int col = 0; col < cols; col++) selCols.append(col);
    // compose the line to be copied
    QString line;
    for(int row = 0; row < selRows.size(); row++) {
        for(int col = 0; col < selCols.size(); col++) {
            line.append(_table->dataString(selRows[row], selCols[col]));
            if (col != selCols.size() - 1) line.append('\t');
        }
        line.append('\n');
    }
    // copy the line in the clipboard
    QApplication::clipboard()->setText(line);
}

void StatS::STableWidget::on_cellsPasting()
{    
    // parse clipboard text
    QVector<QVector<QString> > tab;
    QString clipboard = QApplication::clipboard()->text();
    if (!StatS::Utils::parseClipboard(tab, clipboard)) {
        QMessageBox::critical(this, "Data could not be pasted",
                              "Data present in the clipboard are not  \n"
                              "compatible with StatS table format. The\n"
                              "pasting could not be done.",
                    QMessageBox::Ok);
        return;
    }
    // get selected rows and columns
    QList<int> selCols = _table->selectedColumns();
    QList<int> selRows = _table->selectedRows();
    int rows = _table->rowCount();
    int cols = _table->columnCount();
    // check if there is no selection
    if (selCols.size() == 0 && selRows.size() == 0) {
        // select beginning of the table
        selCols.append(0);
        selRows.append(0);
    }
    // columns pasting
    if (selCols.size() == 0) for (int j = 0; j < cols; j++) selCols.append(j);
    if (selRows.size() == 0) for (int i = 0; i < rows; i++) selRows.append(i);
    // based on selection type, paste the content
    if (selCols.size() == 1 && selRows.size() == 1) {        
        // get the target index
        int iR = selRows[0];
        int iC = selCols[0];
        int rSize = tab.size();
        int cSize = tab[0].size();
        // check if data overflows table sizes
        if (rows < rSize + iR || cols < cSize + iC) {
            if (QMessageBox::Yes == QMessageBox::warning(
                        this, "Data overflow",
                        "Dimension of data to be pasted overflows \n"
                        "current table dimensions. If table is not\n"
                        "going to be resized, overflowed data will\n"
                        "be chopped. Do you want to resize the    \n"
                        "table to accomodate data?",
                        QMessageBox::Yes | QMessageBox::No)) {
            _table->insertRows(rows, rSize + iR - rows);
            _table->insertColumns(cols, cSize + iC - cols);
            }
        }
        // paste data in the table
        for(int i = 0; i < rSize; i++)
            for(int j = 0; j < cSize; j++) {
                QModelIndex index = _table->index(i + iR, j + iC);
                _table->setData(index, tab[i][j]);
            }
        // select pasted cells
        _table->clearSelection();
        for (int i = iR; i < rSize + iR; i++) _table->selectRow(i);
        for (int j = iC; j < cSize + iC; j++) _table->selectColumn(j);
    } else {
        // get the selection size
        int srSize = selRows.size();
        int scSize = selCols.size();
        // compare selection size to paste content size
        // to choose the right upper limit of the loop
        int rSize = (tab.size() > srSize) ? srSize : tab.size();
        int cSize = (tab[0].size() > scSize) ? scSize : tab[0].size();
        // paste data in the table
        for(int i = 0; i < rSize; i++)
            for(int j = 0; j < cSize; j++) {
                QModelIndex index = _table->index(selRows[i],selCols[j]);
                _table->setData(index, tab[i][j]);
            }
        // selected pasted cells
        _table->clearSelection();
        for (int i = 0; i < rSize; i++) _table->selectRow(selRows[i]);
        for (int j = 0; j < cSize; j++) _table->selectColumn(selCols[j]);
    }
    return;
}

void StatS::STableWidget::on_cellsClearing()
{
    QList<int> selCols = _table->selectedColumns();
    QList<int> selRows = _table->selectedRows();
    if (selCols.size() == 0 && selRows.size() == 0) {
        QMessageBox::information(
                    this, "Data not selected",
                    "Please select data to be cleared.",
                    QMessageBox::Ok);
        return;
    }
    // check if only columns are selected and fill all rows in the list
    if (selRows.size() == 0)
        for (int i = 0; i < _table->rowCount(); i++) selRows.append(i);
    // check if only rows are selected and fill all columns in the list
    if (selCols.size() == 0)
        for (int j = 0; j < _table->columnCount(); j++) selCols.append(j);
    // clear all selected cells
    for(int i = 0; i < selRows.size(); i++)
        for(int j = 0; j < selCols.size(); j++) {
            QModelIndex index = _table->index(selRows[i], selCols[j]);
            _table->setData(index, QString());            
        }    
    return;
}

void StatS::STableWidget::on_cellsCutting()
{
    // get a list of selected columns and rows
    QList<int> selCols = _table->selectedColumns();
    QList<int> selRows = _table->selectedRows();
    // check if there is no selection
    if (selCols.size() == 0 && selRows.size() == 0) {
        QMessageBox::information(
                    this, "Data not selected",
                    "Please select data to be cutted.",
                    QMessageBox::Ok);
        return;
    }
    on_cellsCopying();
    on_cellsClearing();
}

void StatS::STableWidget::on_tableSummaryItemClicked(QTreeWidgetItem* item,
                                                     int /*col*/)
{
    // a the reference list of clicked item
    QString refLabel = item->text(1);
    // split reference string, the first one it the header
    QStringList indexes = refLabel.split(',');
    // colNode clicked, select/deselect all columns
    if (indexes.at(0) == "columns") _table->selectColumns();
    // rowNode clicked, select/deselect all rows
    if (indexes.at(0) == "rows") _table->selectRows();
    // a column item has been clicked, select/deselect the column
    if (indexes.at(0) == "column") _table->selectColumn(indexes.at(1).toInt());
    // a row category has been clicked, select all related rows
    if (indexes.at(0) == "row") {
        QList<int> list;
        for (int i = 1; i < indexes.size(); i++)
            list.append(indexes.at(i).toInt());
        _table->clearRowSelection();
        _table->selectRows(list);
    }
    return;
}
